﻿#include <cmath>
#include <codecvt>
#include <iostream>
#include <locale>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>

typedef bool (*IsColorFunction)(int, int, int);
static inline bool IsRedColor(int r, int g, int b) { return (r - g) > 0x20 && (r - b) > 0x20 && (g < b ? b - g : g - b) < 0x18; }

static int *GetColorRect(unsigned char *pixels, int width, int height, IsColorFunction isColor)
{
    std::vector<int> xs;
    std::vector<int> ys;

    // 图像宽度大于64进行缩放
    float scale = width <= 64 ? 1.0f : (64.0f / width); // 控制图像大小, 避免计算量过大

    int scale_width  = width * scale;
    int scale_height = height * scale;
    int scale_size   = scale_width * scale_height;
    std::vector<int> scale_pixels(scale_size, 0);
    int step_x    = width / scale_width;
    int step_y    = height / scale_height;
    int step_size = step_x * step_y;

    // 取缩放后的目标颜色点坐标
    for (int sy = 0; sy < scale_height; sy++)
    {
        for (int sx = 0; sx < scale_width; sx++)
        {
            int x   = sx * step_x;
            int y   = sy * step_y;
            int sum = 0;
            for (int j = 0; j < step_y; j++)
            {
                for (int i = 0; i < step_x; i++)
                {
                    if ((y + j) >= height || (x + i) >= width) continue;

                    int index = ((y + j) * width + (x + i)) * 3;
                    int r     = pixels[index];
                    int g     = pixels[index + 1];
                    int b     = pixels[index + 2];
                    if (isColor(r, g, b)) sum++;
                }
            }
            if (sum >= (step_size / 2))
            {
                scale_pixels[sy * scale_width + sx] = 1;
                xs.push_back(sx);
                ys.push_back(sy);
            }
            else
            {
                scale_pixels[sy * scale_width + sx] = 0;
            }
        }
    }

    int size = xs.size();
    if (size == 0) return nullptr;

    // 取平均最大大小
    auto avg_width  = 0;
    auto avg_height = 0;
    std::vector<int> x_dists(size, 0);
    std::vector<int> y_dists(size, 0);
    for (int i = 0; i < size; i++)
    {
        auto max_width  = 0;
        auto max_height = 0;
        for (int j = i + 1; j < size; j++)
        {
            max_width  = std::max(max_width, std::abs(xs[i] - xs[j]));
            max_height = std::max(max_height, std::abs(ys[i] - ys[j]));
        }
        avg_width += max_width;
        avg_height += max_height;
        x_dists.push_back(max_width);
        y_dists.push_back(max_height);
    }
    avg_width  = avg_width / size * 2;
    avg_height = avg_height / size * 2;

    // 移除过大的点并计算最小包围盒
    auto min_x      = scale_width;
    auto min_y      = scale_height;
    auto max_x      = 0;
    auto max_y      = 0;
    auto valid_size = 0;
    for (int i = 0; i < size; i++)
    {
        auto x      = xs[i];
        auto y      = ys[i];
        auto x_dist = x_dists[i];
        auto y_dist = y_dists[i];
        if (x_dist > avg_width || y_dist > avg_height) continue;
        min_x = min_x < x ? min_x : x;
        min_y = min_y < y ? min_y : y;
        max_x = max_x < x ? x : max_x;
        max_y = max_y < y ? y : max_y;
        valid_size++;
    }
    if (min_x > max_x || min_y > max_y) return nullptr;

    int extend_size         = 20;
    int rect_x              = min_x / scale - extend_size / 2;
    int rect_y              = min_y / scale - extend_size / 2;
    int rect_width          = (max_x - min_x) / scale + extend_size;
    int rect_height         = (max_y - min_y) / scale + extend_size;
    static int s_results[4] = {0, 0, 0, 0};
    s_results[0]            = rect_x < 0 ? 0 : rect_x;
    s_results[1]            = rect_y < 0 ? 0 : rect_y;
    s_results[2]            = rect_width > width ? width : rect_width;
    s_results[3]            = rect_height > height ? height : rect_height;
    return s_results;
}

static std::string GetColorRectDatas(unsigned char *pixels, int width, int height, int fixed_width, int fixed_height, IsColorFunction isColor = IsRedColor)
{
    auto rects = GetColorRect(pixels, width, height, isColor);
    if (rects == nullptr) return "";
    auto x = rects[0];
    auto y = rects[1];
    auto w = rects[2];
    auto h = rects[3];

    // 取目标图像
    std::vector<unsigned char> rect_pixels(w * h * 3);
    for (int j = 0; j < h; j++)
    {
        for (int i = 0; i < w; i++)
        {
            int ix        = x + i;
            int jy        = y + j;
            int dst_index = (j * w + i) * 3;
            int src_index = (jy * width + ix) * 3;
            if (ix < 0 || ix >= width || jy < 0 || jy >= height)
            {
                rect_pixels[dst_index + 0] = 0;
                rect_pixels[dst_index + 1] = 0;
                rect_pixels[dst_index + 2] = 0;
            }
            else
            {
                rect_pixels[dst_index + 0] = pixels[src_index + 0];
                rect_pixels[dst_index + 1] = pixels[src_index + 1];
                rect_pixels[dst_index + 2] = pixels[src_index + 2];
            }
        }
    }

    // stbi_write_jpg("d:/test.jpg", w, h, 3, rect_pixels.data(), w);

    // 缩放到指定大小
    std::vector<unsigned char> dst_pixels(fixed_width * fixed_height * 3);
    float scale_x   = fixed_width * 1.0f / w;
    float scale_y   = fixed_height * 1.0f / h;
    float min_scale = scale_x < scale_y ? scale_x : scale_y;
    int offset_x    = (fixed_width / min_scale - w) / 2;
    int offset_y    = (fixed_height / min_scale - h) / 2;
    for (int j = 0; j < fixed_height; j++)
    {
        for (int i = 0; i < fixed_width; i++)
        {
            int ix        = i / min_scale - offset_x;
            int jy        = j / min_scale - offset_y;
            int dst_index = (j * fixed_width + i) * 3;
            int src_index = (jy * w + ix) * 3;
            if (ix < 0 || ix >= w || jy < 0 || jy >= h)
            {
                dst_pixels[dst_index + 0] = 0;
                dst_pixels[dst_index + 1] = 0;
                dst_pixels[dst_index + 2] = 0;
            }
            else
            {
                dst_pixels[dst_index + 0] = rect_pixels[src_index + 0];
                dst_pixels[dst_index + 1] = rect_pixels[src_index + 1];
                dst_pixels[dst_index + 2] = rect_pixels[src_index + 2];
            }
        }
    }
    // stbi_write_jpg("d:/test1.jpg", fixed_width, fixed_height, 3, dst_pixels.data(), fixed_width);

    std::ostringstream oss;
    for (int y = 0; y < fixed_height; y++)
    {
        for (int x = 0; x < fixed_width; x++)
        {
            int index = (y * fixed_width + x) * 3;
            oss << dst_pixels[index] << " " << dst_pixels[index + 1] << " " << dst_pixels[index + 2] << " ";
        }
        oss << std::endl;
    }
    return oss.str();
}

#include "base64.h"
#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#endif
#ifndef STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#endif

static unsigned char *LoadImageRGB(unsigned char *in_datas, int in_size, int *out_width, int *out_height)
{
    static std::vector<unsigned char> s_pixels;
    int width = 0, height = 0, channel = 0;
    unsigned char *pixels = stbi_load_from_memory(in_datas, in_size, &width, &height, &channel, 3);

    if (pixels == nullptr)
    {
        std::cerr << "STB Reason: " << stbi_failure_reason() << std::endl;
        return nullptr;
    }

    if (out_width != nullptr) *out_width = width;
    if (out_height != nullptr) *out_height = height;
    s_pixels.resize(width * height * 3);
    memcpy(s_pixels.data(), pixels, s_pixels.size());
    stbi_image_free(pixels);
    return s_pixels.data();
}

static unsigned char *LoadImageRGBByBase64ImageData(const std::string &base64_image_data, int *width, int *height)
{
    std::string jpeg_prefix = "data:image/jpeg;base64,";
    std::string png_prefix  = "data:image/png;base64,";
    std::string img_data;
    if (base64_image_data.substr(0, jpeg_prefix.length()) == jpeg_prefix)
    {
        img_data = base64_decode(base64_image_data.substr(jpeg_prefix.length()));
    }
    else if (base64_image_data.substr(0, png_prefix.length()) == png_prefix)
    {
        img_data = base64_decode(base64_image_data.substr(png_prefix.length()));
    }
    else
    {
        img_data = base64_decode(base64_image_data);
    }
    return LoadImageRGB((unsigned char *)(img_data.c_str()), img_data.size(), width, height);
}

static std::string GetRedColorRGB(const std::string &base64_image_data, int fixed_width, int fixed_height)
{
    int width = 0;
    int height = 0;
    auto pixels = LoadImageRGBByBase64ImageData(base64_image_data, &width, &height);
    return GetColorRectDatas(pixels, width, height, fixed_height, fixed_height, IsRedColor);
}

static std::string GetRedColorRect(const std::string &base64_image_data)
{
    int width = 0;
    int height = 0;
    auto pixels = LoadImageRGBByBase64ImageData(base64_image_data, &width, &height);
    auto rects = GetColorRect(pixels, width, height, IsRedColor);
    if (rects == nullptr) return "";
    std::ostringstream oss;
    oss << rects[0] << " " << rects[1] << " " << rects[2] << " " << rects[3] << std::endl;
    return oss.str();
}

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/bind.h>
using namespace emscripten;
EMSCRIPTEN_BINDINGS(ColorImage)
{
    function("GetRedColorRect", &GetRedColorRect);
    function("GetRedColorRGB", &GetRedColorRGB);
}
#endif